1 package org.apache.lucene.store;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.io.IOException;
21 import java.nio.ByteBuffer;
22 import java.nio.MappedByteBuffer;
23 import java.nio.channels.ClosedChannelException;
24 import java.nio.channels.FileChannel;
25 import java.nio.channels.FileChannel.MapMode;
26 import java.nio.file.Path;
27 import java.nio.file.StandardOpenOption;
28 import java.security.AccessController;
29 import java.security.PrivilegedAction;
30 import java.security.PrivilegedExceptionAction;
31 import java.security.PrivilegedActionException;
32 import java.util.Locale;
33 import java.util.concurrent.Future;
34 import java.lang.reflect.Method;
35
36 import org.apache.lucene.store.ByteBufferIndexInput.BufferCleaner;
37 import org.apache.lucene.util.Constants;
38 import org.apache.lucene.util.SuppressForbidden;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 public class MMapDirectory extends FSDirectory {
90 private boolean useUnmapHack = UNMAP_SUPPORTED;
91 private boolean preload;
92
93
94
95
96
97 public static final int DEFAULT_MAX_CHUNK_SIZE = Constants.JRE_IS_64BIT ? (1 << 30) : (1 << 28);
98 final int chunkSizePower;
99
100
101
102
103
104
105
106
107 public MMapDirectory(Path path, LockFactory lockFactory) throws IOException {
108 this(path, lockFactory, DEFAULT_MAX_CHUNK_SIZE);
109 }
110
111
112
113
114
115
116
117 public MMapDirectory(Path path) throws IOException {
118 this(path, FSLockFactory.getDefault());
119 }
120
121
122
123
124
125
126
127
128
129 public MMapDirectory(Path path, int maxChunkSize) throws IOException {
130 this(path, FSLockFactory.getDefault(), maxChunkSize);
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 public MMapDirectory(Path path, LockFactory lockFactory, int maxChunkSize) throws IOException {
155 super(path, lockFactory);
156 if (maxChunkSize <= 0) {
157 throw new IllegalArgumentException("Maximum chunk size for mmap must be >0");
158 }
159 this.chunkSizePower = 31 - Integer.numberOfLeadingZeros(maxChunkSize);
160 assert this.chunkSizePower >= 0 && this.chunkSizePower <= 30;
161 }
162
163
164
165
166 public static final boolean UNMAP_SUPPORTED = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
167 @Override
168 @SuppressForbidden(reason = "Java 9 Jigsaw whitelists access to sun.misc.Cleaner, so setAccessible works")
169 public Boolean run() {
170 try {
171 Class<?> clazz = Class.forName("java.nio.DirectByteBuffer");
172 Method method = clazz.getMethod("cleaner");
173 method.setAccessible(true);
174 return true;
175 } catch (Exception e) {
176 return false;
177 }
178 }
179 });
180
181
182
183
184
185
186
187
188
189
190
191
192
193 public void setUseUnmap(final boolean useUnmapHack) {
194 if (useUnmapHack && !UNMAP_SUPPORTED)
195 throw new IllegalArgumentException("Unmap hack not supported on this platform!");
196 this.useUnmapHack=useUnmapHack;
197 }
198
199
200
201
202
203 public boolean getUseUnmap() {
204 return useUnmapHack;
205 }
206
207
208
209
210
211
212
213 public void setPreload(boolean preload) {
214 this.preload = preload;
215 }
216
217
218
219
220
221 public boolean getPreload() {
222 return preload;
223 }
224
225
226
227
228
229 public final int getMaxChunkSize() {
230 return 1 << chunkSizePower;
231 }
232
233
234 @Override
235 public IndexInput openInput(String name, IOContext context) throws IOException {
236 ensureOpen();
237 Path path = directory.resolve(name);
238 try (FileChannel c = FileChannel.open(path, StandardOpenOption.READ)) {
239 final String resourceDescription = "MMapIndexInput(path=\"" + path.toString() + "\")";
240 final boolean useUnmap = getUseUnmap();
241 return ByteBufferIndexInput.newInstance(resourceDescription,
242 map(resourceDescription, c, 0, c.size()),
243 c.size(), chunkSizePower, useUnmap ? CLEANER : null, useUnmap);
244 }
245 }
246
247
248 final ByteBuffer[] map(String resourceDescription, FileChannel fc, long offset, long length) throws IOException {
249 if ((length >>> chunkSizePower) >= Integer.MAX_VALUE)
250 throw new IllegalArgumentException("RandomAccessFile too big for chunk size: " + resourceDescription);
251
252 final long chunkSize = 1L << chunkSizePower;
253
254
255 final int nrBuffers = (int) (length >>> chunkSizePower) + 1;
256
257 ByteBuffer buffers[] = new ByteBuffer[nrBuffers];
258
259 long bufferStart = 0L;
260 for (int bufNr = 0; bufNr < nrBuffers; bufNr++) {
261 int bufSize = (int) ( (length > (bufferStart + chunkSize))
262 ? chunkSize
263 : (length - bufferStart)
264 );
265 MappedByteBuffer buffer;
266 try {
267 buffer = fc.map(MapMode.READ_ONLY, offset + bufferStart, bufSize);
268 } catch (IOException ioe) {
269 throw convertMapFailedIOException(ioe, resourceDescription, bufSize);
270 }
271 if (preload) {
272 buffer.load();
273 }
274 buffers[bufNr] = buffer;
275 bufferStart += bufSize;
276 }
277
278 return buffers;
279 }
280
281 private IOException convertMapFailedIOException(IOException ioe, String resourceDescription, int bufSize) {
282 final String originalMessage;
283 final Throwable originalCause;
284 if (ioe.getCause() instanceof OutOfMemoryError) {
285
286 originalMessage = "Map failed";
287 originalCause = null;
288 } else {
289 originalMessage = ioe.getMessage();
290 originalCause = ioe.getCause();
291 }
292 final String moreInfo;
293 if (!Constants.JRE_IS_64BIT) {
294 moreInfo = "MMapDirectory should only be used on 64bit platforms, because the address space on 32bit operating systems is too small. ";
295 } else if (Constants.WINDOWS) {
296 moreInfo = "Windows is unfortunately very limited on virtual address space. If your index size is several hundred Gigabytes, consider changing to Linux. ";
297 } else if (Constants.LINUX) {
298 moreInfo = "Please review 'ulimit -v', 'ulimit -m' (both should return 'unlimited'), and 'sysctl vm.max_map_count'. ";
299 } else {
300 moreInfo = "Please review 'ulimit -v', 'ulimit -m' (both should return 'unlimited'). ";
301 }
302 final IOException newIoe = new IOException(String.format(Locale.ENGLISH,
303 "%s: %s [this may be caused by lack of enough unfragmented virtual address space "+
304 "or too restrictive virtual memory limits enforced by the operating system, "+
305 "preventing us to map a chunk of %d bytes. %sMore information: "+
306 "http://blog.thetaphi.de/2012/07/use-lucenes-mmapdirectory-on-64bit.html]",
307 originalMessage, resourceDescription, bufSize, moreInfo), originalCause);
308 newIoe.setStackTrace(ioe.getStackTrace());
309 return newIoe;
310 }
311
312 private static final BufferCleaner CLEANER = new BufferCleaner() {
313 @Override
314 public void freeBuffer(final ByteBufferIndexInput parent, final ByteBuffer buffer) throws IOException {
315 try {
316 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
317 @Override
318 @SuppressForbidden(reason = "Java 9 Jigsaw whitelists access to sun.misc.Cleaner, so setAccessible works")
319 public Void run() throws Exception {
320 final Method getCleanerMethod = buffer.getClass()
321 .getMethod("cleaner");
322 getCleanerMethod.setAccessible(true);
323 final Object cleaner = getCleanerMethod.invoke(buffer);
324 if (cleaner != null) {
325 cleaner.getClass().getMethod("clean")
326 .invoke(cleaner);
327 }
328 return null;
329 }
330 });
331 } catch (PrivilegedActionException e) {
332 throw new IOException("Unable to unmap the mapped buffer: " + parent.toString(), e.getCause());
333 }
334 }
335 };
336 }